package me.dengfengdecao.demo.config.shiro;

import me.dengfengdecao.demo.domain.Permission;
import me.dengfengdecao.demo.domain.Role;
import me.dengfengdecao.demo.domain.User;
import me.dengfengdecao.demo.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.util.Iterator;
import java.util.Set;

/**
 * 身份校验核心类<br/>
 *
 * Created by dengfengdecao on 16/10/1.
 */
public class MyShiroRealm extends AuthorizingRealm {

    public static final Logger log = LoggerFactory.getLogger(MyShiroRealm.class);

    @Resource
    private UserService userService;


    /**
     * 用于对用户进行的操作授权，证明该用户是否允许进行当前操作，如访问某个链接，某个资源文件等。
     * 此方法调用  hasRole,hasPermission的时候才会进行回调.<br/>
     * 授权(权限控制):<br/>
     *  1、如果用户正常退出，缓存自动清空；<br/>
     *  2、如果用户非正常退出，缓存自动清空；<br/>
     *  3、如果我们修改了用户的权限，而用户不退出系统，修改的权限无法立即生效。
     *  （需要手动编程进行实现；放在service进行调用）在权限修改后调用realm中的方法，realm已经由spring管理，
     *  所以从spring中获取realm实例，调用clearCached方法；<br/>
     *  Authorization 是授权访问控制，用于对用户进行的操作授权，证明该用户是否允许进行当前操作，如访问某个链接，某个资源文件等。
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        /*
        * 当没有使用缓存的时候，不断刷新页面的话，这个代码会不断执行，
        * 当其实没有必要每次都重新设置权限信息，所以我们需要放到缓存中进行管理；
        * 当放到缓存中时，这样的话，doGetAuthorizationInfo就只会执行一次了，
        * 缓存过期之后会再次执行。
        */
        log.info("######## 执行Shiro权限认证 ########");

        String username = (String) principalCollection.getPrimaryPrincipal();

        log.info("doGetAuthorizationInfo: username <== " + username);

        // 获取当前用户信息
        User user = userService.findByUsername(username);

        log.debug("doGetAuthorizationInfo: user <== " + user);
        if (user != null) {
            // 权限信息对象authorizationInfo,用来存放查出的用户的所有的角色（role）及权限（permission）
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

            // 当前用户的角色集合
            Iterator<Role> iterator = user.getRoles().iterator();
            while (iterator.hasNext()) {
                authorizationInfo.addRole(iterator.next().getRole());
            }

            // 当前用户的角色对应的所有权限
            Set<Role> roles = user.getRoles();
            for (Role role : roles) {
                Iterator<Permission> iterator1 = role.getPermissions().iterator();
                while (iterator1.hasNext())
                    authorizationInfo.addStringPermission(iterator1.next().getPermission());
            }

            return authorizationInfo;
        }

        // 返回null的话，就会导致任何用户访问被拦截的请求时，都会自动跳转到unauthorizedUrl指定的地址
        return null;
    }

    /**
     * 用来验证用户身份
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("######## 执行Shiro身份认证 ########");

        // 获取用户登录的用户名
        String username = (String) authenticationToken.getPrincipal();

        log.debug("doGetAuthenticationInfo: 当前用户username <== " + username);

        // 实际项目中，这里可以根据实际情况做缓存，如果不做，Shiro自己也是有时间间隔机制，2分钟内不会重复执行该方法
        User user = userService.findByUsername(username);
        log.debug("doGetAuthenticationInfo: 从数据库查出的用户user <== " + user);
        if (user == null) {
            return null;
        } else {
            //UsernamePasswordToken对象用来存放提交的登录信息
            UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

            log.info("doGetAuthenticationInfo: 验证当前Subject时获取到token为：" + token);

            // 加密方式
            // 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配，如果觉得人家的不好可以自定义实现
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),
                user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName());

            // 如果你是进行明文进行编码的话，那么使用使用如下方式：
           /* SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user.getUsername(), //用户名
                user.getPassword(), //密码
                getName()  //realm name
            );*/

            return authenticationInfo;
        }
    }
}
